<!DOCTYPE html>
<br /><html lang="zh-CN"><head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>小说搜索与下载</title>
    <script id="FileSaver">(function (global, factory) {
  if (typeof define === "function" && define.amd) {
    define([], factory);
  } else if (typeof exports !== "undefined") {
    factory();
  } else {
    var mod = {
      exports: {}
    };
    factory();
    global.FileSaver = mod.exports;
  }
})(this, function () {
  "use strict";

  /*
  * FileSaver.js
  * A saveAs() FileSaver implementation.
  *
  * By Eli Grey, http://eligrey.com
  *
  * License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)
  * source  : http://purl.eligrey.com/github/FileSaver.js
  */
  // The one and only way of getting global scope in all environments
  // https://stackoverflow.com/q/3277182/1008999
  var _global = typeof window === 'object' && window.window === window ? window : typeof self === 'object' && self.self === self ? self : typeof global === 'object' && global.global === global ? global : void 0;

  function bom(blob, opts) {
    if (typeof opts === 'undefined') opts = {
      autoBom: false
    };else if (typeof opts !== 'object') {
      console.warn('Deprecated: Expected third argument to be a object');
      opts = {
        autoBom: !opts
      };
    } // prepend BOM for UTF-8 XML and text/* types (including HTML)
    // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF

    if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
      return new Blob([String.fromCharCode(0xFEFF), blob], {
        type: blob.type
      });
    }

    return blob;
  }

  function download(url, name, opts) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.responseType = 'blob';

    xhr.onload = function () {
      saveAs(xhr.response, name, opts);
    };

    xhr.onerror = function () {
      console.error('could not download file');
    };

    xhr.send();
  }

  function corsEnabled(url) {
    var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker

    xhr.open('HEAD', url, false);

    try {
      xhr.send();
    } catch (e) {}

    return xhr.status >= 200 && xhr.status <= 299;
  } // `a.click()` doesn't work for all browsers (#465)


  function click(node) {
    try {
      node.dispatchEvent(new MouseEvent('click'));
    } catch (e) {
      var evt = document.createEvent('MouseEvents');
      evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
      node.dispatchEvent(evt);
    }
  } // Detect WebView inside a native macOS app by ruling out all browsers
  // We just need to check for 'Safari' because all other browsers (besides Firefox) include that too
  // https://www.whatismybrowser.com/guides/the-latest-user-agent/macos


  var isMacOSWebView = _global.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent);
  var saveAs = _global.saveAs || ( // probably in some web worker
  typeof window !== 'object' || window !== _global ? function saveAs() {}
  /* noop */
  // Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView
  : 'download' in HTMLAnchorElement.prototype && !isMacOSWebView ? function saveAs(blob, name, opts) {
    var URL = _global.URL || _global.webkitURL;
    var a = document.createElement('a');
    name = name || blob.name || 'download';
    a.download = name;
    a.rel = 'noopener'; // tabnabbing
    // TODO: detect chrome extensions & packaged apps
    // a.target = '_blank'

    if (typeof blob === 'string') {
      // Support regular links
      a.href = blob;

      if (a.origin !== location.origin) {
        corsEnabled(a.href) ? download(blob, name, opts) : click(a, a.target = '_blank');
      } else {
        click(a);
      }
    } else {
      // Support blobs
      a.href = URL.createObjectURL(blob);
      setTimeout(function () {
        URL.revokeObjectURL(a.href);
      }, 4E4); // 40s

      setTimeout(function () {
        click(a);
      }, 0);
    }
  } // Use msSaveOrOpenBlob as a second approach
  : 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) {
    name = name || blob.name || 'download';

    if (typeof blob === 'string') {
      if (corsEnabled(blob)) {
        download(blob, name, opts);
      } else {
        var a = document.createElement('a');
        a.href = blob;
        a.target = '_blank';
        setTimeout(function () {
          click(a);
        });
      }
    } else {
      navigator.msSaveOrOpenBlob(bom(blob, opts), name);
    }
  } // Fallback to using FileReader and a popup
  : function saveAs(blob, name, opts, popup) {
    // Open a popup immediately do go around popup blocker
    // Mostly only available on user interaction and the fileReader is async so...
    popup = popup || open('', '_blank');

    if (popup) {
      popup.document.title = popup.document.body.innerText = 'downloading...';
    }

    if (typeof blob === 'string') return download(blob, name, opts);
    var force = blob.type === 'application/octet-stream';

    var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari;

    var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);

    if ((isChromeIOS || force && isSafari || isMacOSWebView) && typeof FileReader !== 'undefined') {
      // Safari doesn't allow downloading of blob URLs
      var reader = new FileReader();

      reader.onloadend = function () {
        var url = reader.result;
        url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;');
        if (popup) popup.location.href = url;else location = url;
        popup = null; // reverse-tabnabbing #460
      };

      reader.readAsDataURL(blob);
    } else {
      var URL = _global.URL || _global.webkitURL;
      var url = URL.createObjectURL(blob);
      if (popup) popup.location = url;else location.href = url;
      popup = null; // reverse-tabnabbing #460

      setTimeout(function () {
        URL.revokeObjectURL(url);
      }, 4E4); // 40s
    }
  });
  _global.saveAs = saveAs.saveAs = saveAs;

  if (typeof module !== 'undefined') {
    module.exports = saveAs;
  }
});
</script>
  <style type="text/css">* {user-select: auto !important; -webkit-user-select: auto !important;}</style></head>
  <body data-theme="light">
    <button class="toggle-theme" onclick="toggleTheme()">
      <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" id="light" display="block">
        <circle cx="12" cy="12" r="10" fill="currentColor"></circle>
      </svg>
      <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" id="dark" display="none">
        <path fill="currentColor" d="M10 2c-1.82 0-3.53.5-5 1.35C7.99 5.08 10 8.3 10 12s-2.01 6.92-5 8.65C6.47 21.5 8.18 22 10 22c5.52 0 10-4.48 10-10S15.52 2 10 2"></path>
      </svg>
    </button>
    <h1 id="title">小说搜索与下载</h1>
    <div id="container" class="container">
      <form class="search-form" id="search-form">
        <input type="text" id="book-search" placeholder="请输入小说名称">
        <button type="button" onclick="searchBooks()">
          <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" width="24" height="24" viewBox="0 0 24 24" display="inherit">
            <path d="M16.296 16.996a8 8 0 11.707-.708l3.909 3.91-.707.707-3.909-3.909zM18 11a7 7 0 00-14 0 7 7 0 1014 0z"></path>
          </svg>
        </button>
      </form>
      <div id="book-list" class="book-list"></div>
      <div id="book-info" class="book-info">
        <img src="" alt="封面" id="book-cover">
        <div class="book-info-container">
          <p id="book_id" style="display: none"></p>
          <p id="book_info" style="display: none"></p>
          <p id="download_lock" style="display: none"></p>
          <h2 id="book-title"></h2>
          <p><strong>作者：</strong><span id="book-author"></span></p>
          <p><strong>分类：</strong><span id="book-category"></span></p>
          <p id="book-description"></p>
          <button class="download-btn" onclick="downloadBook()">下载</button>
          <select id="charset-selector" class="charset-selector">
            <option value="utf-8">UTF-8</option>
            <!-- <option value="gbk">GBK</option> -->
          </select>
        </div>
      </div>
    </div>
    <!-- 进度条弹窗 -->
    <div id="progress-modal" class="progress-modal" style="display: none">
      <div class="progress-box">
        <h3 id="progress-title">下载进度</h3>
        <div class="progress-bar">
          <div id="progress-bar-fill" class="progress-bar-fill"></div>
        </div>
        <div class="progress-text">
          <p id="progress-percentage">0%</p>
          <p id="progress-count">0/0</p>
        </div>
        <button id="progress-cancel" class="progress-cancel">取消</button>
        <button id="progress-submit" class="progress-submit" style="display: none">确定</button>
        <button id="button-resave" class="button-resave" style="display: none" onclick="reSave()">手动保存</button>
        <button id="button-redownload" class="button-redownload" style="display: none" onclick="reDownload()">重新下载</button>
      </div>
    </div>
    <script>
      const apiNode = 'https://fqapi.wwwweb.top'

      // 设置页面主题
      function setTheme(theme) {
        document.body.dataset.theme = theme
        document.getElementById(theme).setAttribute('display', 'block')
        document.getElementById(theme === 'dark' ? 'light' : 'dark').setAttribute('display', 'none')
      }

      // 自动检测浏览器主题并设置页面主题
      function setThemeBasedOnPreference() {
        const savedTheme = localStorage.getItem('theme')
        if (savedTheme) {
          setTheme(savedTheme)
        } else {
          const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
            .matches
            ? 'dark'
            : 'light'
          setTheme(systemTheme)
        }
      }

      // 切换主题并保存到localStorage
      function toggleTheme() {
        const currentTheme = document.body.dataset.theme || 'light'
        const newTheme = currentTheme === 'dark' ? 'light' : 'dark'
        setTheme(newTheme)
        localStorage.setItem('theme', newTheme)
      }

      // 初始化主题
      setThemeBasedOnPreference()
      // 初始化form事件
      document.getElementById('search-form').addEventListener('submit', function (event) {
        event.preventDefault();
        searchBooks();
      })

      function matchId(url) {
        const regex = /(?:\/page\/(\d{19})|[\?&]book_id=(\d{19}))/;
        const match = url.match(regex);

        if (match) {
          return match[1] || match[2];
        } else {
          return null;
        }
      }
      // 搜索书籍
      async function searchBooks() {
        const query = document.getElementById('book-search').value
        if (!query) { return; }
        let _book_id = matchId(query);
        if (_book_id) {
          return await showBookInfo(_book_id);
        }
        const offset = 0 // 默认偏移量
        const tabType = 3 // 默认搜索类型为书籍
        const url = `${apiNode}/search?query=${query}&offset=${offset}&tab_type=${tabType}`

        try {
          const response = await fetch(url)
          const data = await response.json()
          console.log(data)
          const resp =
            data.search_tabs.find(tab => tab.tab_type === 3)?.data || []
          let searchResults = []
          for (let i of resp) {
            let obj = (i.book_data || [])[0]
            if (obj) searchResults.push(obj)
          }
          displaySearchResults(searchResults)
        } catch (error) {
          console.error('搜索失败:', error)
        }
      }

      // 显示搜索结果
      function displaySearchResults(results) {
        const bookList = document.getElementById('book-list')
        bookList.innerHTML = '' // 清空列表
        results.forEach(result => {
          const bookItem = document.createElement('div')
          bookItem.classList.add('book-item')
          bookItem.dataset.bookId = result.book_id
          bookItem.innerHTML = `
            <div>${result.book_name}</div>
            <div>${result.author}</div>
          `
          bookItem.addEventListener('click', () => showBookInfo(result.book_id))
          bookList.appendChild(bookItem)
        })
        const bookInfo = document.getElementById('book-info')
        bookInfo.style.display = 'none' // 隐藏书籍详情区域
      }

      // 显示书籍详细信息
      async function showBookInfo(bookId) {
        console.log("saveAs", saveAs)
        const bookList = document.getElementById('book-list')
        bookList.innerHTML = '' // 清空搜索列表
        const url = `${apiNode}/info?book_id=${bookId}`
        try {
          const response = await fetch(url)
          const data = await response.json()
          const bookData = data.data || {} // 假设返回的数据在`data`字段中

          // 填充书籍信息到页面
          const bookInfo = document.getElementById('book-info')
          bookInfo.style.display = 'flex' // 显示书籍详情区域

          document.getElementById('book_id').innerHTML = bookId
          document.getElementById('book_info').innerHTML = JSON.stringify(bookData)
          document.getElementById('book-title').textContent =
            bookData.book_name || '未知标题'
          document.getElementById('book-author').textContent =
            bookData.author || '未知作者'
          document.getElementById('book-category').textContent =
            bookData.category || '未知分类'
          document.getElementById('book-description').textContent =
            bookData.abstract || '暂无简介'
          document.getElementById('book-cover').src =
            bookData.thumb_url || 'default_cover.jpg' // 默认封面
        } catch (error) {
          console.error('获取书籍详情失败:', error)
          alert('获取书籍详情失败')
        }
      }

      const chunkArray = (arr, size) => {
        const result = [];
        for (let i = 0; i < arr.length; i += size) {
          result.push(arr.slice(i, i + size));
        }
        return result;
      }
      
      function escapeRegExp(string) {
        return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
      }

      // 下载书籍
      async function downloadBookExecutor() {
        window.downloadResult = ''
        const Lock = document.getElementById('download_lock');
        const charset = document.getElementById('charset-selector').value;
        console.log(charset);

        let taskId = `task_${Date.now()}`
        console.log(taskId)
        if (Lock.innerHTML.trim() !== '') {
          console.warn(Lock.innerHTML.trim() !== '',  '下载按钮重复点击');
          return;
        } else {
          Lock.innerHTML = taskId;
        }

        const bookId = document.getElementById('book_id').innerHTML;
        if (!bookId) {
          alert('请先选择一本书籍！');
          return;
        }

        const catalogUrl = `${apiNode}/catalog?book_id=${bookId}`;
        let catalog;
    
        try {
          const response = await fetch(catalogUrl);
          const body = await response.json();
          catalog = body.data?.item_data_list;
        } catch (error) {
          console.error('获取目录失败', error);
        }

        if (!catalog) {
          alert('获取目录失败或目录为空');
          console.error(catalog);
          return;
        }

        let progress = showProgressWindow("下载", catalog.length);
        progress.updateTitle("准备中...");
    
        const item_ids = catalog.map(item => item.item_id);
        const result = {};
        let cursor = 0;

        const maxConcurrentRequests = 16;

        const downloadChunks = async () => {
          while (item_ids.length > 0) {
            const currentChunk = item_ids.splice(0, maxConcurrentRequests);
            const promises = currentChunk.map(async (item_id) => {
              let retry = 0;
              while (true) {
                if (progress.isCancelled) {
                  return;
                }
                const response = await fetch(`${apiNode}/content?item_id=${item_id}`);
                const body = await response.json();
                if (body.data?.content) {
                  result[item_id] = body.data;
                  cursor++;
                  progress.updateProgressBar(cursor);
                  progress.updateTitle("缓存中... | " + (catalog[cursor] || {}).title);
                  break;
                } else {
                  retry++;
                  if (retry > 3) {
                    result[item_id] = body.data;
                    break;
                  }
                }
              }
            });
            
            await Promise.all(promises);
            if (progress.isCancelled) {
              Lock.innerHTML = '';
              return false;
            }
          }
          return true;
        };

        let isSuccess = await downloadChunks();
        if (!isSuccess) {
          return;
        }

        console.log(Object.keys(result).length);
        progress.updateTitle("处理章节中...");
        cursor = 0;
        progress.updateProgressBar(cursor);
        let content = "";
        let bookData = JSON.parse(document.getElementById('book_info').innerHTML);
    
        content += bookData.book_name + '\n';
        content += `作者：${bookData.author}\n`;
        content += `分类：${JSON.parse(bookData.category_v2).map(item => item.Name).join('、')}\n`;
        content += `主角：${JSON.parse(bookData.roles || '[]').join("、")}\n`
        content += `简介：${bookData.abstract}\n`;
        content += `${bookData.copyright_info.replace("，如有任何疑问，请通过“我的-意见反馈”告知我们", '')}。\n\n\n`;

        let volname;
        for (let i of catalog) {
          let data = result[i.item_id];
          cursor++;
          if (data && volname != data.novel_data.volume_name) {
            content += `${data.novel_data.volume_name.replace("：默认", '')}\n\n`;
            volname = data.novel_data.volume_name;
          }
          let text = `${i.title}\n\u3000\u3000` + htmlToTextArray((data?.content || '<p>章节内容为空</p>').replace(new RegExp(escapeRegExp(i.title), 'g'), '')).join('\n\u3000\u3000').trim() + '\n\n\n'; // .replace(`${i.title}\n\u3000\u3000${i.title}`, i.title) + '\n\n\n';
          content += text;
          progress.updateProgressBar(cursor);
        }

        progress.updateTitle('执行完成，正在保存');
        const file = `${bookData.book_name}_${bookData.author}.txt`
        window.downloadResult = {
          content,
          file
        };
        progress.complete();
        const blob = new Blob([new TextEncoder(charset).encode(content)], { type: `text/plain;charset=${charset}` });
        saveAs(blob, file);
        Lock.innerHTML = '';
      }

      async function downloadBook() {
        try {
          await downloadBookExecutor();
        } catch (e) {
          console.error(e)
        }
      }
      
      function reSave() {
        // 检查 window.downloadResult 是否存在
        if (!window.downloadResult) {
          console.error("window.downloadResult is not defined.");
          return;
        }

        // 创建一个 data URI
        // const dataURI = "data:text/plain;charset=utf-8," + encodeURIComponent(window.downloadResult.content);

        alert('请全选新页面内容进行手动保存');
        // 在新页面中打开 data URI
        const newWindow = window.open();
        
        if (newWindow) {
          newWindow.document.write(`<pre>${window.downloadResult.content}</pre>`); // 显示文本内容
          newWindow.document.close();
        } else {
          console.error("Failed to open a new window.");
        }
      }

      function reDownload() {
        const blob = new Blob([new TextEncoder('utf-8').encode(window.downloadResult.content)], { type: `text/plain;charset=utf-8` });
        saveAs(blob, window.downloadResult.file);
      }

      function getAllTextElements(element, textArray) {
        // 获取当前元素的所有子节点
        const childNodes = element.childNodes;

        // 遍历每一个子节点
        childNodes.forEach(node => {
          // 如果是文本节点，将其内容加入到数组中
          if (node.nodeType === Node.TEXT_NODE) {
            textArray.push(node.textContent.trim());
          } else if (node.nodeType === Node.ELEMENT_NODE) {
            // 如果是元素节点，递归调用该函数处理其子元素
            getAllTextElements(node, textArray);
          }
        });
      }

      function htmlToTextArray(html) {
        // 创建一个div，用于存放html内容
        const div = document.createElement('div');

        // 修改div的内容为给定的html
        div.innerHTML = html;

        // 获取div中的所有文本内容
        let textArray = [];
        getAllTextElements(div, textArray);

        return textArray;
      }
      async function multiDownloadExecutor(book_id, item_ids, depth=0) {
        if (!item_ids.length) { return {}; }
        if (depth > 3) { 
          alert("下载失败")
          throw new Error("try max num")
        }
        try {
          const response = await fetch(`${apiNode}/multi-content?book_id=${book_id}&item_ids=${item_ids.join(',')}`)
          const result = await response.json()
          // console.log(item_ids, result)
          return result.data
        } catch (err) {
          console.warn("error downloading...", book_id, item_ids, "retry", depth)
          return await multiDownloadExecutor(book_id, item_ids, depth+1)
        }
      }

      function showProgressWindow(title, total) {
        const progressModal = document.getElementById('progress-modal');
        const progressBarFill = document.getElementById('progress-bar-fill');
        const progressPercentage = document.getElementById('progress-percentage');
        const progressCount = document.getElementById('progress-count');
        const progressTitle = document.getElementById('progress-title');
        const progressCancelBtn = document.getElementById('progress-cancel');
        const progressSubmitBtn = document.getElementById('progress-submit');
        const resaveButton = document.getElementById('button-resave');
        const redlButton = document.getElementById('button-redownload');

        // 显示进度窗口
        progressModal.style.display = 'flex'
        console.log('display=flex')
        progressTitle.textContent = title;
        progressCount.textContent = `0/${total}`;
        progressPercentage.textContent = '0%';
        progressBarFill.style.width = '0%';

        let isCancelled = false;

        // 操作对象
        let obj = {
          total,
          title,
          isCancelled,
          updateTitle(title) {
            const progressTitle = document.getElementById('progress-title');
            progressTitle.textContent = title
            this.title = title
          },
          updateProgressBar(current) {
            if (this.isCancelled) return;

            const percentage = ((current / this.total) * 100).toFixed(2);
            progressBarFill.style.width = `${percentage}%`;
            progressPercentage.textContent = `${percentage}%`;
            progressCount.textContent = `${current}/${total}`;
          },
          cancel() {
            this.isCancelled = true;
            progressModal.style.display = 'none';
            document.getElementById('download_lock').innerHTML = '';
          },
          complete() {
            progressSubmitBtn.style = '';
            resaveButton.style = '';
            redlButton.style = '';
            progressCancelBtn.style.display = 'none';
          },
          finish() {
            progressModal.style.display = 'none';
            progressCancelBtn.style = '';
            progressSubmitBtn.style.display = 'none';
            redlButton.style.display = 'none';
            resaveButton.style.display = 'none';
            document.getElementById('download_lock').innerHTML = '';
          }
        };

         // 按钮事件
        progressCancelBtn.onclick = () => {
          obj.cancel();
        };
        progressSubmitBtn.onclick = () => {
          obj.finish();
        };

        return obj
      }

      async function checkMultiDownloadSupport(bookId, itemId) {
        try {
          const response = await fetch(`${apiNode}/multi-content?book_id=${bookId}&item_ids=${itemId}`)
          const _ = await response.json()
          console.log(_)
          if (_.data && _.data.length != 0) {
            return true
          }
          return false
        } catch (err) {
          console.warn(err)
          return false
        }
      }
    </script>
    <style>
      body {
        font-family: Arial, sans-serif;
        margin: 0;
        padding: 0;
        background-color: var(--bg-color);
        color: var(--text-color);
        transition: background-color 0.3s, color 0.3s;
      }

      :root {
        --bg-color: #fff;
        --text-color: #000;
        --button-bg: #007bff;
        --button-text: #fff;
        --button-hover-bg: #0056b3;
        --info-bg: #f9f9f9;
        --download-bg: #28a745;
        --download-text: #fff;
        --download-hover-bg: #1e7e34;
        --toggle-bg: #6c757d;
        --toggle-text: #fff;
        --toggle-hover-bg: #5a6268;
      }

      [data-theme='dark'] {
        --bg-color: #121212;
        --text-color: #e0e0e0;
        --button-bg: #3498db;
        --button-text: #fff;
        --button-hover-bg: #2980b9;
        --info-bg: #1e1e1e;
        --download-bg: #4caf50;
        --download-text: #fff;
        --download-hover-bg: #3e8e41;
        --toggle-bg: #95a5a6;
        --toggle-text: #fff;
        --toggle-hover-bg: #7f8c8d;
      }

      #title {
        text-align: center;
        padding: 20px;
      }

      .container {
        display: flex;
        flex-direction: column;
        align-items: center;
        padding: 20px;
      }

      .search-form {
        display: flex;
        justify-content: center;
        /* width: 800px; */
        margin-bottom: 20px;
      }

      .search-form input {
        padding: 10px;
        font-size: 16px;
        border: 1px solid #ccc;
        border-radius: 15px 0 0 15px;
        /* width: 250px; */
        /* max-width: 85%; */
        transition: width 0.3s;
      }

      .search-form button {
        padding: 10px 20px;
        font-size: 16px;
        border: none;
        width: 40%;
        max-width: 80px;
        background-color: var(--button-bg);
        color: var(--button-text);
        border-radius: 0 15px 15px 0;
        cursor: pointer;
        transition: background-color 0.3s;
      }

      .search-form button:hover {
        background-color: var(--button-hover-bg);
      }

      .book-list {
        display: flex;
        flex-direction: column;
        align-items: center;
        width: 90%;
        max-width: 600px;
      }

      .book-item {
        display: flex;
        justify-content: space-between;
        width: 100%;
        padding: 10px;
        border-bottom: 1px solid #ccc;
        cursor: pointer;
      }

      .book-item:hover {
        background-color: #777;
      }

      .book-info {
        display: none;
        flex-direction: row;
        border: 1px solid #ccc;
        border-radius: 10px;
        padding: 20px;
        width: 60%;
        background-color: var(--info-bg);
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
        margin-top: 20px;
        transition: background-color 0.3s;
      }

      .book-info img {
        display: flex;
        max-width: 40%;
        border-radius: 10px;
        margin-right: 20px;
      }

      .book-info h2 {
        margin: 0 0 10px;
      }

      .book-info p {
        margin: 5px 0;
        text-align: justify;
      }

      .book-info .download-btn {
        padding: 10px 20px;
        font-size: 16px;
        border: none;
        background-color: var(--download-bg);
        color: var(--download-text);
        border-radius: 5px;
        cursor: pointer;
        transition: background-color 0.3s;
      }

      .book-info .download-btn:hover {
        background-color: var(--download-hover-bg);
      }
      
      .book-info .charset-selector {
        padding: 10px 20px;
        font-size: 16px;
        border: none;
        background-color: var(--download-bg);
        color: var(--download-text);
        border-radius: 5px;
      }

      .toggle-theme {
        position: fixed;
        top: 20px;
        right: 20px;
        padding: 10px;
        font-size: 16px;
        border: none;
        background-color: var(--toggle-bg);
        color: var(--toggle-text);
        border-radius: 5px;
        cursor: pointer;
        transition: background-color 0.3s;
      }

      .toggle-theme:hover {
        background-color: var(--toggle-hover-bg);
      }

      .book-info-container {
        flex: 1;
      }

      @media (orientation: portrait) {
        /*
        .search-form input {
          width: 85%;
        }
        */
        .book-info {
          width: 90%;
          flex-direction: column;
        }
        .book-info img {
          max-width: 100%;
          border-radius: 10px;
          margin-bottom: 10px;
        }
      }
  
      .progress-modal {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.5);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 1000;
      }

      .progress-box {
        background: var(--bg-color);
        padding: 20px;
        border-radius: 8px;
        text-align: center;
        width: 300px;
      }

      .progress-bar {
        width: 100%;
        background: #f3f3f3;
        border-radius: 4px;
        overflow: hidden;
        margin-bottom: 10px;
      }

      .progress-bar-fill {
        height: 20px;
        background: #4caf50;
        width: 0%;
        transition: width 0.2s;
      }

      .progress-text {
        margin-bottom: 10px;
      }

      .progress-cancel {
        padding: 5px 10px;
        background: #ff5722;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
      }

      .button-resave, .button-redownload, .progress-submit {
        padding: 5px 10px;
        background: var(--download-bg);
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        transition: background-color 0.3s;
      }
      
      .button-resave:hover, .button-redownload:hover, .progress-submit:hover {
        background: var(--download-hover-bg);
      }
    </style>
  

</body></html>